// RaspBLServ
//
// Permet de communiquer avec RaspBL sur le Raspberry pour lui envoyer
// l'OS.
//
// Première argument : périphérique (/dev/...)
// Deuxième argument : fichier à charger (kernel.img)
//
// Voici le protocole utilisée :
// 1. RaspBL envoie au serveur (chargé d'envoyer le noyau) la chaine "RDY" pour
// dire qu'il est prêt
// 2. Le serveur répond en deux temps :
//	- d'abord la taille du noyau sur 32 octets
//	- ensuite le noyau en soit
//


#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>

//
// Ouvre le périphérique
//
// Baud rate : 115200 baud
// Taille des maots : 8 bits
// pas stop bits
// pas de parité
// pas de flow control
//
// Renvoie le FD en cas de succès, -1 en cas d'echec
int dev_open(const char* path)
{
	int fd;
	struct termios options;

	// On ouvre le pérophérique
	fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
	if (fd == -1)
	{
		return -1;
	}
	if (fcntl(fd, F_SETFL, 0) < 0)
	{
		return fd;
	}

	// On configure le périphérique
	if (tcgetattr(fd, &options) < 0)
	{
		return fd;
	}
	// Configuration des attributs
	if ( (cfsetispeed(&options, B115200) < 0)
		|| (cfsetospeed(&options, B115200) < 0) )
	{
		return fd;
	}
	options.c_cflag = ( CS8 | CREAD  | CLOCAL );
	options.c_oflag = 0;
	options.c_iflag = 0;
	options.c_lflag = 0;

	options.c_cc[VTIME] = 0;
    options.c_cc[VMIN] = 1;

	// Écriture des attributs
	if (tcflush(fd, TCIOFLUSH) < 0)
	{
		return fd;
	}

	if (tcsetattr(fd, TCSANOW, &options) < 0)
	{
		return fd;
	}

	return fd;
}

//
// Fonction bloquante qui attend ue RaspBL lui en envoie RDY pour signifier qu'il est prêt
// Renvoie 0 en cas de succès, -1 en cas d'echec.
//
// Le Raspberry Pi envoie parfois des ocets nuls pendant sont initialisation, on les ignores
// ici.
int dev_wait_rdy(int dev_fd)
{
	const unsigned int BUFFER_SIZE = 256u;
	char tmp_buffer[BUFFER_SIZE];
	char buffer[BUFFER_SIZE];
	unsigned int k;

	k = 0u;
	for ( ;; )
	{
		unsigned int i;
		int res;

		res = read(dev_fd, tmp_buffer, BUFFER_SIZE);
		if (res < 0)
		{
			return -1;
		}
		for (i = 0u; i < res; i++)
		{
			if (tmp_buffer[i] != 0)
			{
				buffer[k++] = tmp_buffer[i];
				if (k == 3u)
				{ // On a recu trois caractères.
					if ((strncmp("RDY", buffer, 3) == 0))
					{ // C'est les bons !
						return 0;
					}
					else
					{ // C'est pas bon
						return -1;
					}
				}
			}
		}
	}
}

//
// Envoyer le noyau
int dev_send_kernel(int dev_fd, int kern_fd)
{
	int res;
	const unsigned int BUFFER_SIZE = 8096u;
	uint8_t buffer[BUFFER_SIZE];
	struct stat st;
	uint32_t kern_size;

	// On lit la taille du fichier contenant le noyau
	fstat(kern_fd, &st);
	kern_size = st.st_size;

	// On envoie cette taille
	res = write(dev_fd, &kern_size, sizeof(kern_size));
	if (res < 0)
	{
		return -1;
	}

	// On envoie le noyau
	while ((res = read(kern_fd, buffer, BUFFER_SIZE)) > 0)
	{
		int pos = 0;
		while (res > 0)
		{
			int wr = write(dev_fd, &buffer[pos], res);
			if (wr < 0)
			{
				return -1;
			}
			res -= wr;
			pos += wr;
		}
	}

	if (res < 0)
	{
		return -1;
	}

	return 0;
}

//
// Quitte et ferme les fichier
void do_exit(int dev_fd, int kern_fd, int status)
{
	if (dev_fd != -1)
	{
		close(dev_fd);
	}
	if (kern_fd != -1)
	{
		close(kern_fd);
	}
	exit(status);
}

//
// Fonction principale
int main(int argc, char **argv)
{
	int dev_fd = -1;
	int kern_fd = -1;
	

	// On vérifie le nombre d'arguments
	if (argc != 3)
	{
		printf("Mauvaise utilisation.\n");
		printf("Syntax : <command> <device> <fichier>\n");
		printf("Exemple : raspbootcom /dev/tty.usb0 kernel.img\n");
		do_exit(dev_fd, kern_fd, EXIT_FAILURE);
	}

	// On ouvre le périphérique
	printf("## Ouverture du périphérique : ");
	dev_fd = dev_open(argv[1]);
	if (dev_fd < 0)
	{
		perror("Erreur à l'ouverture du device - ");
		do_exit(dev_fd, kern_fd, EXIT_FAILURE);
	}
	printf("OK\n");

	printf("## Attente que RaspBL soir prêt : ");
	if (dev_wait_rdy(dev_fd) < 0)
	{
		perror("Erreur lors de l'attente de RDY - ");
		do_exit(dev_fd, kern_fd, EXIT_FAILURE);
	}
	printf("OK\n");

	// Ouverture du fichier contenant le noyau
	printf("## Ouverture du fichier contenant le noyau : ");
	kern_fd = open(argv[2], O_RDONLY);
	if (kern_fd < 0)
	{
		perror("Erreur à l'ouverture du fichier noyau - ");
		do_exit(dev_fd, kern_fd, EXIT_FAILURE);
	}
	printf("OK\n");

	// On envoit l'OS
	printf("## On envoie le noyau : ");
	if (dev_send_kernel(dev_fd, kern_fd) < 0)
	{
		perror("Erreur à l'envoie du noyau - ");
		do_exit(dev_fd, kern_fd, EXIT_FAILURE);
	}
	printf("OK\n");

	// On a finit
	do_exit(dev_fd, kern_fd, EXIT_SUCCESS);

	return 0;
}
